﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Data;
using System.Reflection;
using SimpleMap.Binders;
using SimpleMap.Extractors;
using System.Threading.Tasks;
using SimpleMap.ClassFactories;
using SimpleMap.Setters;

namespace SimpleMap
{

    /// <summary>
    /// Projects data from an IDatareader into a target POCO 
    /// </summary>
    public class Projector<T>
    {
        public Projector(IValueBindingExtractor<T> extractor, IClassFactory<T> factory)
        {

            if (factory == null)
            {
                throw new ArgumentNullException("factory");
            }

            if (extractor == null)
            {
                throw new ArgumentNullException("extractor");
            }

            _factory = factory;
            _elements = extractor.Extract();
        }

        public Projector(List<IValueBinding<T>> elements, IClassFactory<T> factory)
        {
            if (factory == null) 
                { 
                throw new ArgumentNullException("factory"); 
            }
            
            if ((elements == null) || (_elements.Count == 0))
            {
                throw new ApplicationException("at least one binding must be provided.");
            }
            
            _factory = factory;
            _elements = elements;
        }

        /// <summary>
        /// Creates a generic projector
        /// </summary>
        /// <param name="useCodeGeneration">if true the projector will use il code for property setting.
        /// </param>
        /// <returns>Projector</returns>
        public static Projector<T> CreateProjector(bool useCodeGeneration) 
        {

            IPropertyValueSetterFactory<T> setterFactory = (IPropertyValueSetterFactory<T>)new PropertySetterValueFactory<T>(useCodeGeneration);
            IClassFactory<T> classFactory = new ClassFactory<T>();

            IValueBindingExtractor<T> extractor = extractor = (IValueBindingExtractor<T>)new PropertyBindingExtractor<T>(setterFactory);
           
            Projector<T> p = new Projector<T>(extractor, classFactory);

            return p;

        
        }
 
        
        /// <summary>
        /// Fills a collection with projected objects
        /// </summary>
        /// <param name="collection">collection to fill</param>
        /// <param name="reader">data reader to use as data source</param>
        public void Project(ICollection<T> collection, IDataReader reader)
        {


            IEnumerator<T> e = new ProjectionEnumerator<T>(reader, _elements, _factory);

            while (e.MoveNext()) 
            {
                collection.Add(e.Current);
            }
            

        }

        
        /// <summary>
        /// Creates an enumerable cursor for a projection.
        /// </summary>
        /// <param name="reader">data reader to use as data source</param>
        /// <returns>IEumerable</returns>
        public ProjectionCursor<T> GetCursor(IDataReader reader)
        {
            return new ProjectionCursor<T>(new ProjectionEnumerator<T>(reader, _elements, _factory));
        }

        private List<IValueBinding<T>> _elements;
    
        private IClassFactory<T> _factory;
    }

    
    /// <summary>
    /// Enumeraion facade for a projector
    /// </summary>
    public class ProjectionCursor<T> : IEnumerable<T> 
    {

        ProjectionEnumerator<T> _e;

        internal ProjectionCursor(ProjectionEnumerator<T> e) 
        {
            _e = e;
        }


        #region IEnumerable<TargetException> Members

        public IEnumerator<T> GetEnumerator()
        {
            return _e;
        }

        #endregion

        #region IEnumerable Members

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return _e;
        }

        #endregion
    }
}
